// <unistd.h>: 包含 POSIX 操作系统 API 的函数声明。
// 例如：fork() 用于创建新进程，execlp() 用于执行新程序，close() 用于关闭文件描述符。
#include <unistd.h>
// <stdlib.h>: 标准库头文件。
// 例如：exit() 用于终止进程。
#include <stdlib.h>
// <stdio.h>: 标准输入输出头文件。
// 例如：perror() 用于打印与上一个系统调用错误相关的信息。
#include <stdio.h>

// 函数定义: mplayer_play
// 功能: 创建一个子进程来执行 mplayer 命令，从而播放指定的歌曲文件。
//       这种方式可以让主程序（歌词显示程序）继续运行，而音乐在后台播放。
// 参数:
//   char *song_path: 一个指向字符串的指针，该字符串包含了要播放的歌曲文件的路径。
//                    例如 "./song/我的歌.mp3"。
// 返回值:
//   void: 此函数不返回任何值。
void mplayer_play(char * song_path)
{
	// pid_t 是一个用于表示进程ID (Process ID) 的数据类型，在 <unistd.h> (通常通过 <sys/types.h>) 中定义。
	// 声明一个 pid_t 类型的变量 pid，用于存储 fork() 系统调用的返回值。
	pid_t pid;

	// 调用 fork() 系统函数来创建一个新的进程。
	// fork() 的行为特点:
	//  - 对父进程：如果成功，fork() 返回新创建的子进程的PID (一个正整数)。
	//  - 对子进程：如果成功，fork() 返回 0。
	//  - 如果失败：fork() 返回 -1，并且不会创建子进程。失败原因可能是系统资源不足等。
	pid=fork();

	// 检查 fork() 的返回值以判断其执行结果。
	// 情况 1: fork() 失败。
	if(pid<0) // 如果 pid 小于 0 (即 -1)，表示创建子进程失败。
	{
		// perror() 函数会根据当前的 errno 值 (一个全局变量，存储了上一个系统调用发生的错误代码)
		// 打印出用户提供的字符串 (这里是 "fork")，后跟一个冒号和一个描述该错误的标准错误信息。
		// 例如，如果错误是 "No child processes"，它可能打印 "fork: No child processes"。
		perror("fork");
		// 在这种情况下，父进程可以决定如何处理这个错误，例如记录日志或退出。
		// 此处父进程会继续执行到函数末尾。
	}
	// 情况 2: 当前执行的是子进程的代码。
	else if(pid==0) // 如果 pid 等于 0，表示这段代码是在新创建的子进程中执行的。
	{
		// 在子进程中，我们通常希望执行一个新的程序 (这里是 mplayer)。
		// 在执行新程序之前，有时需要做一些清理或准备工作。

		// close(1): 关闭文件描述符 1。文件描述符 1 通常代表标准输出 (stdout)。
		//           关闭它意味着子进程 (mplayer) 的任何标准输出都不会显示在控制台上。
		//           这样做是为了防止 mplayer 的输出干扰歌词显示程序的界面。
		close(1);
		// close(2): 关闭文件描述符 2。文件描述符 2 通常代表标准错误输出 (stderr)。
		//           关闭它意味着子进程 (mplayer) 的任何错误信息也不会显示在控制台上。
		close(2);

		// execlp() 是 exec 函数族的一员，用于执行一个新的程序，并用该新程序替换当前进程的映像。
		// 如果 execlp 成功，当前子进程的代码 (即 execlp 调用之后的所有代码) 将不会被执行，
		// 因为子进程已经被新的 mplayer 程序完全替代了。
		// 参数说明:
		//   "mplayer": 要执行的命令或程序名。execlp 会在 PATH 环境变量指定的目录中查找此程序。
		//   "mplayer": 传递给新程序的第一个参数 (argv[0])，按惯例是程序名本身。
		//   "-slave":  传递给 mplayer 的一个命令行参数，使其以从属模式运行。这样可以更容易地通过管道控制mplayer（尽管此程序没用管道）。
		//   "-quiet":  传递给 mplayer 的另一个参数，使其在运行时尽量减少不必要的输出信息。
		//   song_path: 指向包含歌曲文件路径的字符串，作为参数传递给 mplayer。
		//   NULL:      execlp 的参数列表必须以 NULL 指针结束。
		execlp("mplayer","mplayer","-slave","-quiet",song_path,NULL);

		// 如果 execlp 调用失败 (例如找不到 mplayer 程序，或者没有执行权限)，
		// 它会返回 -1，并且子进程会继续执行 execlp 调用之后的代码。
		// 在这种情况下，子进程应该退出，以避免执行不期望的代码。
		// perror("execlp mplayer"); // 可以在这里添加错误打印，了解为何execlp失败
		exit(0); // exit(0) 表示子进程正常退出 (即使 execlp 失败了，子进程本身也算完成了它的任务——尝试执行mplayer)。
				 // 返回非0值 (如 exit(1)) 可以表示子进程因错误而退出。
	}
	// 情况 3: 当前执行的是父进程的代码。
	else // 如果 pid 大于 0，表示 fork() 成功，并且这段代码是在父进程中执行的。
		 // pid 的值就是新创建的子进程的 PID。
	{
		// 父进程通常在这里继续执行它自己的任务。
		// 在这个项目中，父进程是歌词显示程序，它会继续运行歌词同步逻辑。
		// 子进程 (mplayer) 会独立于父进程在后台播放音乐。
		// 父进程可以通过子进程的 PID (存储在 pid 变量中) 来管理子进程，
		// 例如使用 waitpid()等待子进程结束 (但此程序中没有这样做，允许mplayer独立运行)。
		; // 空语句，表示父进程在此处不做任何特殊操作，直接继续。
	}
	// 函数结束，父进程会从这里返回到 main 函数中的调用点。
}